package com.xxx.servlet;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.xxx.anno.AnnoAutowired;
import com.xxx.anno.AnnoController;
import com.xxx.anno.AnnoRequestMapping;
import com.xxx.anno.AnnoRequestParam;
import com.xxx.anno.AnnoService;

/**
 * 自定义DispatcherServlet
 */
public class DispatcherServlet extends HttpServlet {

	// 存放“.class”全路径的list
	List<String> classNames = new ArrayList<>();

	// 存储类实例化对象的map，作为IOC容器：key为注解的value，value为对象
	Map<String, Object> beans = new HashMap<>();

	// 存储路径和方法的映射关系
	Map<String, Object> handlerMap = new HashMap<>();

	// 初始化容器：扫描包、实例化对象、依赖注入、路径映射
	public void init(ServletConfig config) throws ServletException {
		// 1.扫描包
		scanPackage("com.xxx");

		// 2.实例化对象
		doInstance();

		// 3.依赖注入
		doAutowired();

		// 4.路径映射
		urlMapping();
	}

	// 4.路径映射：http://127.0.0.1:8080/mvc/demo/test ——> method
	private void urlMapping() {
		for (Map.Entry<String, Object> entry : beans.entrySet()) {
			// 从ioc容器里获取对象实例
			Object instance = entry.getValue();

			// 通过对象实例，得到clazz对象
			Class<?> clazz = instance.getClass();

			if (clazz.isAnnotationPresent(AnnoController.class)) { // 只对Controller类
				// 先获取Controller类的path
				AnnoRequestMapping classAnnoRequestMapping = clazz.getAnnotation(AnnoRequestMapping.class);
				String classPath = classAnnoRequestMapping.value(); // Controller类上的路径：/demo

				// 再获取类的方法上的路径
				Method[] methods = clazz.getMethods();
				// 循环所有的method，判断method上是否包含@AnnoRequestMapping注解，如果有，则进行处理，否则是普通方法
				for (Method method : methods) {
					if (method.isAnnotationPresent(AnnoRequestMapping.class)) {
						AnnoRequestMapping methodAnnoRequestMapping = method.getAnnotation(AnnoRequestMapping.class);
						String methodPath = methodAnnoRequestMapping.value();
						handlerMap.put(classPath + methodPath, method);
					}
				}
			}
		}
	}

	// 3.依赖注入：就是将Controller里使用@AnnoAutowired注解的Service成员变量实例设置到该类的成员变量里
	private void doAutowired() {
		for (Map.Entry<String, Object> entry : beans.entrySet()) {
			// 从ioc容器里获取对象实例
			Object instance = entry.getValue();

			// 通过对象实例，得到clazz对象
			Class<?> clazz = instance.getClass();

			// 根据类上的不同注解，分别进行依赖注入
			if (clazz.isAnnotationPresent(AnnoController.class)) { // Controller类上注入Service
				// 获取到该类的成员变量
				Field[] fields = clazz.getDeclaredFields();
				// 循环成员变量列表，判断成员变量上是否包含@AnnoAutowired注解，如果包含，则进行注入
				for (Field field : fields) {
					try {
						if (field.isAnnotationPresent(AnnoAutowired.class)) {
							AnnoAutowired annoAutowired = field.getAnnotation(AnnoAutowired.class);
							String key = annoAutowired.value();
							// 由于@AnnoAutowired注解的成员变量是private的，需要修改访问属性
							field.setAccessible(true);
							field.set(instance, beans.get(key)); // 将依赖的bean（Service对象）注入到Controller
						}
					} catch (IllegalAccessException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			} else if (clazz.isAnnotationPresent(AnnoService.class)) { // Service类上注入Mapper，此处没有

			}
		}
	}

	// 2.将classNames中的类进行实例化
	private void doInstance() {
		for (String className : classNames) {
			// 去掉className后面的“.class”（也可以在scanPackage里去掉）
			String cn = className.replace(".class", "");

			try {
				// 根据className，使用forName()方法创建Class对象
				Class<?> clazz = Class.forName(cn);

				// 根据类上的不同注解，分别进行实例化，然后放入IOC容器里
				if (clazz.isAnnotationPresent(AnnoController.class)) {
					// 获取注解上的value()，作为iocMap的key
					AnnoRequestMapping requestMapping = clazz.getAnnotation(AnnoRequestMapping.class);
					String key = requestMapping.value();
					Object instance = clazz.newInstance();
					beans.put(key, instance); // bean容器：key为类注解的value
				} else if (clazz.isAnnotationPresent(AnnoService.class)) {
					// 获取注解上的value()，作为iocMap的key
					AnnoService annoservice = clazz.getAnnotation(AnnoService.class);
					String key = annoservice.value();
					Object instance = clazz.newInstance();
					beans.put(key, instance);
				}
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}

	// 1.扫描指定的package
	private void scanPackage(String basePackage) {
		// 将package中的“.”转换成“/”
		URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
		// 根据url获取该路径的全路径名，作为basePath
		String basePath = url.getFile();
		// 根据basePath创建File对象
		File baseFile = new File(basePath);
		// 获取该basePath下的所有文件和文件夹相对路径名
		String[] filePathArr = baseFile.list();

		// 循环每个filePath（相对路径）
		for (String filePath : filePathArr) {
			// 根据当前file的全路径名，创建File对象
			File file = new File(basePath + filePath);
			// 判断是否为文件夹，如果是，则递归调用
			if (file.isDirectory()) {
				scanPackage(basePackage + "." + filePath);
			} else {
				classNames.add(basePackage + "." + file.getName()); // list：com.xxx.controller.DemoController.class
			}
		}
	}

	// 处理业务
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String uri = request.getRequestURI(); // /mvc/demo/test
		String context = request.getContextPath();
		String path = uri.replace(context, ""); // /demo/test
		
		// 从handlerMap里获取该path对象的method
		Method method = (Method) handlerMap.get(path);
		Object instance = beans.get("/"+path.split("/")[1]); // beans.get("/demo");
		
		// 参数处理
		Object[] args = handParam(request, response, method);
		
		try {
			method.invoke(instance, args);
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	// 参数处理
	private Object[] handParam(HttpServletRequest request, HttpServletResponse response, Method method) {
		// 获取当前method的所有参数
		Class<?>[] paramClazzs = method.getParameterTypes();
		// new 一个参数数组
		Object[] args = new Object[paramClazzs.length];
		
		int args_i = 0;
		int index = 0;
		for(Class<?> paramClazz : paramClazzs) {
			if(ServletRequest.class.isAssignableFrom(paramClazz)) {
				args[args_i++] = request;
			}
			if(ServletResponse.class.isAssignableFrom(paramClazz)) {
				args[args_i++] = response;
			}
			
			Annotation[] paramAnnotations = method.getParameterAnnotations()[index];
			if(paramAnnotations.length>0) {
				for(Annotation paramAnnotation : paramAnnotations) {
					if(AnnoRequestParam.class.isAssignableFrom(paramAnnotation.getClass())) {
						AnnoRequestParam annoRequestParam = (AnnoRequestParam) paramAnnotation;
						// 找到AnnoRequestParam注解里的参数：name和age
						args[args_i++] = request.getParameter(annoRequestParam.value());
					}
				}
			}
			index++;
		}
		return args;
	}
}
